package com.pvdnc.dxhook;

import android.content.Context;
import android.telecom.Call;
import android.util.Log;
import android.util.Pair;

import com.google.dexmaker.Code;
import com.google.dexmaker.DexMaker;
import com.google.dexmaker.Local;
import com.google.dexmaker.MethodId;
import com.google.dexmaker.TypeId;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class DxHook {
    private static final String TAG=DxHook.class.getSimpleName();

    private static final TypeId<?> DXHOOK_TYPE=TypeId.get(DxHook.class);

    private static final TypeId<?> METHOD_TYPE=TypeId.get(Method.class);

    public static Object handleHookedMethod(String calleeInfoJson,Object thisRef,Object[] args){
        Log.d(TAG,"callee info:\n\t"+calleeInfoJson);
        CalleeInfo calleeInfo=GsonUtils.fromJson(calleeInfoJson,CalleeInfo.class);
        Log.d(TAG,"thisRef:"+thisRef);
        Object oldObject= OldObjectCache.INSTANCE.getOldObject(calleeInfo.getOldObjectHashCode());
        Log.d(TAG,"oldObject:"+oldObject);//这个在Replace模式下是null，不影响逻辑

        HookCallback callback= XHookManager.INSTANCE.getCallback(calleeInfo);
        try {
            if(callback==null) {//直接调用原过程
                Log.e(TAG,"no callback registered,use default");
                callback=new HookCallback();
            }
            /*这里两种模式走相同的逻辑是因为CalleeInfo#mOldObjHashCode的存在，
             不同对象实例的CalleeInfo是独立的
             所以对于Override模式同样适用*/
            Method oldMethod= XHookManager.INSTANCE.getOldMethod(calleeInfo,thisRef);
            //前插桩
            callback.beforeHookedMethod(oldMethod,oldObject,thisRef,args);

            boolean skip=!callback.needToCallOldMethod();
            if(skip){
                Log.d(TAG,"skip calling oldMethod:\n\t"+oldMethod);
                return callback.getResult();
            }
            //调用原过程
            Object retValue = oldMethod.invoke(thisRef,args);
            Log.d(TAG, "ret value:" + retValue);
            //后插桩
            callback.setResult(retValue);
            callback.afterHookedMethod(oldMethod,oldObject,thisRef,args);

            callback.reset();//恢复状态（前一次可能取消调用）
            //返回
            return callback.getResult();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static MethodId<?,?> getHandlerMethodId(){
        return DXHOOK_TYPE.getMethod(TypeId.OBJECT,
                "handleHookedMethod",
                TypeId.STRING,TypeId.OBJECT,MakerUtils.OBJ_ARRAY_TYPE);
    }

    public static final MethodId<?,?> HANDLER_METHOD_ID=getHandlerMethodId();

    private static final MethodId<?,?> getHandlerInvokerMethodId(TypeId<?> type){
        return DXHOOK_TYPE.getMethod(TypeId.OBJECT,
                "handleHookedMethod",
                TypeId.STRING,TypeId.OBJECT,MakerUtils.OBJ_ARRAY_TYPE);
    }

    public static class Replace{
        public static CalleeInfo createHookClass(Context context,Method method) throws Exception {
            File optDir=context.getDir("dexMaker",Context.MODE_PRIVATE);
            return createHookClass(context.getClassLoader(),
                    optDir,
                    method);
        }

        public static CalleeInfo createHookClass(ClassLoader parent, File optDir, Method method) throws Exception {
            //构造重写类型
            DexMaker maker = new DexMaker();
            Class<?> declClass = method.getDeclaringClass();
            String newClassName = "R_" + declClass.getSimpleName() + "_" + method.getName();
            TypeId<?> type = TypeId.get("L" + newClassName + ";");
            maker.declare(type, newClassName + ".generated",
                    Modifier.PUBLIC, TypeId.OBJECT);
            //添加默认构造器（基于Object）
            MakerUtils.addDefaultConstructor(maker, type);

            CalleeInfo calleeInfo= createReplaceMethod(maker, type, method);

            ClassLoader cl = maker.generateAndLoad(parent,
                   optDir);
            Class<?> hookClass = cl.loadClass(newClassName);
            //备份
            Method replaceMethod=hookClass.getDeclaredMethod(method.getName(),method.getParameterTypes());
            Method backupMethod= NativeHelper.replaceMethod(replaceMethod,method);
            XHookManager.INSTANCE.cacheBackup(backupMethod);

            return calleeInfo;
        }

        private static CalleeInfo createReplaceMethod(DexMaker maker,TypeId<?> type,Method method) throws Exception {
            MethodUtils.ensureInitialized(method);
            NativeHelper.removeFinalFlag(method);

            CalleeInfo calleeInfo=new CalleeInfo(CalleeInfo.HookMode.REPLACE,method);

            //构造参数TypeId列表
            MethodId<?,?> overrideMethod=MakerUtils.getMethodId(type,method);
            TypeId<?> retType=overrideMethod.getReturnType();
            boolean isValueTypeReturned=ValueTypes.getReferenceTypeFromType(retType)!=null;

            Code replaceCode= maker.declare(overrideMethod,
                    method.getModifiers()/*复制原方法的java access_flags*/);

            Local calleeInfoJsonRef=replaceCode.newLocal(TypeId.STRING);
            Local<?> thisRef=null;
            if(!Modifier.isStatic(method.getModifiers())) {
                thisRef = replaceCode.getThis(type);
            }
            else{
                thisRef=replaceCode.newLocal(TypeId.OBJECT);
            }
            Local castRetValue=replaceCode.newLocal(TypeId.OBJECT);//handleHookedMethod的返回值

            Local refedRetValue=null;
            if(isValueTypeReturned){
                refedRetValue=replaceCode.newLocal(ValueTypes.getReferenceTypeFromType(retType));
            }

            Local retValue=replaceCode.newLocal(retType);
            Local argsRef=MakerUtils.buildParamArray(overrideMethod,replaceCode);
            //变量定义完成
            if(Modifier.isStatic(method.getModifiers())) {
                replaceCode.loadConstant(thisRef, null);
            }

            replaceCode.loadConstant(calleeInfoJsonRef,calleeInfo.toJson());
            replaceCode.loadConstant(castRetValue,null);
            replaceCode.loadConstant(retValue,null);
            //调用handleBeforeHookedMethod
            replaceCode.invokeStatic(HANDLER_METHOD_ID,castRetValue,
                    calleeInfoJsonRef,thisRef,argsRef);

            //返回
            if(TypeId.VOID.equals(retType)){
                replaceCode.returnVoid();
            }else {
                //目标：从引用类型到值类型（把castRetValue转换成值类型包含null）
                MethodId toValue = ValueTypes.toValueFromType(retType);
                if (toValue != null) {
                    Log.d(TAG,"need to toValue convert");
                    replaceCode.cast(refedRetValue,castRetValue);
                    replaceCode.invokeVirtual(toValue, retValue, refedRetValue);
                }
                //MethodId fromValue = ValueTypes.getValueFromType(retType);
                //replaceCode.move(retValue,castRetValue);
                replaceCode.cast(retValue,castRetValue);
                replaceCode.returnValue(retValue);
            }

            return calleeInfo;
        }
    }

    public static class OverrideResult{
        public final Class<?> mOverrideClass;
        public final Map<Method, CalleeInfo> mMethodCalleeInfoMap;

        private OverrideResult(Map<Method,CalleeInfo> methodCalleeInfoMap,Class<?> overrideClass){
            mMethodCalleeInfoMap=methodCalleeInfoMap;
            mOverrideClass=overrideClass;
        }
    }

    public static class Override{
        public static String getSuperCallerMethodName(Method method){
            return "call_"+method.getName();
        }

        private static boolean checkOverridable(Method method){
            int modifiers=method.getModifiers();
            boolean cannotOverride= Modifier.isStatic(modifiers)
                    || Modifier.isPrivate(modifiers)
                    || Modifier.isNative(modifiers);
            return !cannotOverride;
        }

        public static OverrideResult createOverrideClass(Context context, Object oldObject) throws Exception {
            Class<?> cls=oldObject.getClass();

            //移除final标签
            NativeHelper.setExtendable(cls);

            //构造重写类型
            DexMaker maker=new DexMaker();
            TypeId<?> superType=TypeId.get(cls);
            String newClassName="R_"+cls.getSimpleName();
            TypeId<?> type=TypeId.get("L"+newClassName+";");
            maker.declare(type,newClassName+".generated",
                    Modifier.PUBLIC,superType);
            //添加默认构造器（基于Object）
            Map<Method,CalleeInfo> methodCalleeInfoMap=new HashMap<>();
            MakerUtils.addDefaultConstructor(maker,type);
            for (Method method:cls.getDeclaredMethods()){
                if(!checkOverridable(method))
                    continue;
                Pair<Method,CalleeInfo> pair= createOverrideMethod(maker,oldObject,type,method);
                methodCalleeInfoMap.put(pair.first,pair.second);
            }

            ClassLoader cl= maker.generateAndLoad(context.getClassLoader(),
                    context.getDir("dexMaker",Context.MODE_PRIVATE));
            Class<?> overrideClass= cl.loadClass(newClassName);
            return new OverrideResult(methodCalleeInfoMap,overrideClass);
        }

        private static Pair<Method,CalleeInfo> createOverrideMethod(DexMaker maker, Object oldObject, TypeId<?> type, Method method) throws Exception {
            //确保可重写
            if (!checkOverridable(method))
                throw new IllegalArgumentException("method is direct called that cannot be hooked");
            Log.d(TAG, "attempt to create override method for method:\n\t" + method);
            NativeHelper.removeFinalFlag(method);
            //缓存调用者信息
            CalleeInfo calleeInfo = new CalleeInfo(oldObject,method);
            OldObjectCache.INSTANCE.cacheOldObject(oldObject);

            //构造参数TypeId列表
            MethodId<?, ?> overrideMethod = MakerUtils.getMethodId(type, method);
            TypeId<?> overrideRetType = overrideMethod.getReturnType();
            boolean isValueTypeReturned = ValueTypes.getReferenceTypeFromType(overrideRetType) != null;

            Code overrideCode = maker.declare(overrideMethod, Modifier.PUBLIC);

            Local calleeInfoJsonRef = overrideCode.newLocal(TypeId.STRING);
            Local<?> thisRef = overrideCode.getThis(type);
            Local castRetValue = overrideCode.newLocal(TypeId.OBJECT);//handler的返回

            Local refedRetValue = null;
            if (isValueTypeReturned) {
                refedRetValue = overrideCode.newLocal(ValueTypes.getReferenceTypeFromType(overrideRetType));
            }

            Local retValue = overrideCode.newLocal(overrideRetType);
            Local argsRef = MakerUtils.buildParamArray(overrideMethod, overrideCode);
            //变量定义完成

            overrideCode.loadConstant(calleeInfoJsonRef, calleeInfo.toJson());
            overrideCode.loadConstant(castRetValue, null);
            overrideCode.loadConstant(retValue, null);
            //调用handleBeforeHookedMethod
            overrideCode.invokeStatic(HANDLER_METHOD_ID, castRetValue,
                    calleeInfoJsonRef, thisRef, argsRef);

            //返回
            if (TypeId.VOID.equals(overrideRetType)) {
                overrideCode.returnVoid();
            } else {
                //目标：从引用类型到值类型（把castRetValue转换成值类型包含null）
                MethodId toValue = ValueTypes.toValueFromType(overrideRetType);
                if (toValue != null) {
                    Log.d(TAG, "need to toValue convert");
                    overrideCode.cast(refedRetValue, castRetValue);
                    overrideCode.invokeVirtual(toValue, retValue, refedRetValue);
                }
                //MethodId fromValue = ValueTypes.getValueFromType(overrideRetType);

                overrideCode.cast(retValue,castRetValue);
                overrideCode.returnValue(retValue);
            }


            //生成调用super method
            createSuperCallerMethod(maker, type, method);

            return new Pair<>(method,calleeInfo);
        }

        private static void createSuperCallerMethod(DexMaker maker,TypeId<?> type,Method method) throws Exception {
            TypeId<?> superType=TypeId.get(method.getDeclaringClass());
            TypeId<?> superRetType=TypeId.get(method.getReturnType());
            //定义caller方法
            String methodName=getSuperCallerMethodName(method);
            MethodId<?,?> callSuperMethod=MakerUtils.getMethodId(type,
                    methodName,method);

            Code callSuperCode= maker.declare(callSuperMethod,Modifier.PUBLIC);
            Local superThisRef=callSuperCode.getThis(type);
            Local superRetValue=callSuperCode.newLocal(superRetType);

            MethodId<?,?> superMethod=MakerUtils.getMethodId(superType,method);
            Local[] argsArray=MakerUtils.buildParamArrayForReflect(superMethod,callSuperCode);
            callSuperCode.loadConstant(superRetValue,null);
            MakerUtils.createCallSuperByReflect(callSuperCode,
                    superThisRef,superMethod,superRetValue,
                    argsArray);
        }
    }



}
